package com.jiguiquan.www.service.impl;

import com.jiguiquan.starter.common.exception.ZidanApiException;
import com.jiguiquan.www.service.JsInvokeService;
import com.jiguiquan.www.service.JsScriptFactory;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author jiguiquan
 * @Email jiguiquan@haier.com
 * @Data 2023-06-30 13:50
 */
@Service("jsInvokeService")
@Slf4j
public class JsInvokeServiceimpl implements JsInvokeService {
    @Value("${js.use_js_sandbox}")
    private boolean useJsSandbox;

    private NashornSandbox sandbox;
    private ScriptEngine engine;
    private ExecutorService jsExecutor;

    protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>();

    private final ReentrantLock evalLock = new ReentrantLock();

    protected boolean useJsSandbox() {
        return useJsSandbox;
    }

    @PostConstruct
    public void init() {
        if (useJsSandbox()) {
            sandbox = NashornSandboxes.create();
            this.jsExecutor = Executors.newFixedThreadPool(4);  // 生产中自定义线程池
            sandbox.setExecutor(jsExecutor);
            sandbox.setMaxCPUTime(100);  //JS执行程序允许的最大CPU时间（以毫秒为单位）
            sandbox.setMaxMemory(1024 * 1024); //JS执行程序线程可以分配的最大内存
            sandbox.allowNoBraces(false); // 允许出现大括号
            sandbox.allowLoadFunctions(true); // 允许加载函数
            sandbox.setMaxPreparedStatements(30); // LRU初缓存的初始化大小，默认为0
        } else {
            engine = new ScriptEngineManager().getEngineByName("nashorn");
        }
    }

    @Override
    public UUID eval(String scriptBody) throws ScriptException {
        UUID scriptId = UUID.randomUUID();
        String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
        String jsScript = JsScriptFactory.generateJsScript(functionName, scriptBody);
        log.info("最终生成的JS脚本为：{}", jsScript);

        try {
        evalLock.lock();
            if (useJsSandbox()) {
                sandbox.eval(jsScript);
            } else {
                engine.eval(jsScript);
            }
        } finally {
            evalLock.unlock();
        }
        scriptIdToNameMap.put(scriptId, functionName);
        return scriptId;
    }

    @Override
    public Object invokeFunction(UUID scriptId, Object... args) throws ScriptException, NoSuchMethodException {
        String functionName = scriptIdToNameMap.get(scriptId);
        if (functionName == null) {
            throw ZidanApiException.create("500", "js function no exist", "js函数不存在");
        }
        if (useJsSandbox()) {
            return sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
        } else {
            return ((Invocable) engine).invokeFunction(functionName, args);
        }
    }

    @Override
    public void release(UUID scriptId) throws ScriptException {
        String functionName = scriptIdToNameMap.get(scriptId);
        if (functionName != null) {
            scriptIdToNameMap.remove(scriptId);
            if (useJsSandbox()) {
                sandbox.eval(functionName + " = undefined;");
            } else {
                engine.eval(functionName + " = undefined;");
            }
        }
    }
}
